﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using Jacaranda.Data;

namespace Jacaranda.Model
{
    public abstract class ConditionalExpression
    {
        internal abstract string GenerateSql(Type propertyEntityType);

        internal DbCommand CreateSelectEntityCommand(Type entityType, bool isTopOne, SortCriteria sort, SearchPaginationCriteria page)
        {
            DbCommand command = DataAccess.Create().CreateDbCommand();

            List<ConditionalExpression> allNodes = GetAllNodes(null);
            int parameterIndex = 0;

            foreach (ParameterGenerationNode expression in allNodes.Where(i => i is ParameterGenerationNode))
            {
                expression.ParameterIndex = parameterIndex++;
                expression.Command = command;
            }

            foreach (InClause criteria in allNodes.Where(i => i is InClause))
            {
                criteria.TopMostKeyFieldName = Metadata.Type(entityType).PrimaryKey.FieldName;

                if (criteria.IsTopMost)
                {
                    criteria.DemandedType = entityType;
                }
            }


            string fields;

            if (Metadata.Type(entityType).Create)
            {
                fields = isTopOne ? "TOP 1 *" : "*";
            }
            else
            {
                StringBuilder b = new StringBuilder();
                foreach (FieldMetadata field in Metadata.Type(entityType).Fields.Where(i => !i.Ignore))
                {
                    b.AppendFormat("{0} AS {1}, ", field.ExpressionName, field.PropertyName);
                }

                fields = b.ToString(0, b.Length - 2);
            }

           
            if (sort != null)
            {
                sort.EntityType = entityType;
            }


            string sql = null;

            if (page != null)
            {
                sql = DataAccess.Create().CreatePagingSelectStatement(this, entityType, fields, sort, page);
            }
            else
            {
               sql = string.Format("SELECT {0} FROM {1} {2} {3}",
               fields,
               Metadata.Type(entityType).TableName,
               (this is SelectAllCriteria) ? null : ("WHERE " + GenerateSql(entityType)),
               (sort == null) ? null : sort.GenerateSql());
            }


            command.CommandText = sql;
            return command;
        }

        internal DbCommand CreateExistsCommand(Type entityType)
        {
            DbCommand command = DataAccess.Create().CreateDbCommand();

            AssignNodes(entityType, null, command);
            command.CommandText = string.Format("IF EXISTS (SELECT NULL FROM {0} {1}) SELECT 1 ELSE SELECT 0", Metadata.Type(entityType).TableName, (this is SelectAllCriteria) ? null : ("WHERE " + GenerateSql(entityType)));

            return command;
        }

        internal DbCommand CreateSelectCountCommand(Type entityType)
        {
            DbCommand command = DataAccess.Create().CreateDbCommand();

            AssignNodes(entityType, null, command);
            command.CommandText = string.Format("SELECT COUNT(*) FROM {0} {1}", Metadata.Type(entityType).TableName, (this is SelectAllCriteria) ? null : ("WHERE " + GenerateSql(entityType)));

            return command;
        }

        internal DbCommand CreateDeleteCommand(Type entityType)
        {
            DbCommand command = DataAccess.Create().CreateDbCommand();

            AssignNodes(entityType, null, command);
            command.CommandText = string.Format("DELETE FROM {0} WHERE {1}", Metadata.Type(entityType).TableName, GenerateSql(entityType));

            return command;
        }

        internal DbCommand CreateUpdateCommand(Type entityType, Update[] updates)
        {
            DbCommand command = DataAccess.Create().CreateDbCommand();

            AssignNodes(entityType, updates, command);


            StringBuilder b = new StringBuilder();

            foreach (Update update in updates)
            {
                b.Append(update.GenerateSql(entityType)).Append(", ");
            }

            string updateStatement = b.ToString();

            if (updateStatement.EndsWith(", "))
            {
                updateStatement = updateStatement.Substring(0, updateStatement.Length - 2);
            }


            command.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2}", Metadata.Type(entityType).TableName, updateStatement, GenerateSql(entityType));

            return command;
        }

        private void AssignNodes(Type entityType, Update[] updates, DbCommand command)
        {
            List<ConditionalExpression> allNodes = GetAllNodes(updates);
            int parameterIndex = 0;

            foreach (ParameterGenerationNode expression in allNodes.Where(i => i is ParameterGenerationNode))
            {
                expression.ParameterIndex = parameterIndex++;
                expression.Command = command;
            }

            foreach (Update update in allNodes.Where(i => i is Update))
            {
                update.ParameterIndex = parameterIndex++;
                update.Command = command;
            }

            foreach (InClause criteria in allNodes.Where(i => i is InClause))
            {
                criteria.TopMostKeyFieldName = Metadata.Type(entityType).PrimaryKey.FieldName;

                if (criteria.IsTopMost)
                {
                    criteria.DemandedType = entityType;
                }
            }
        }

        internal List<ConditionalExpression> GetAllNodes(Update[] updates)
        {
            List<ConditionalExpression> children = new List<ConditionalExpression>();

            Queue<ConditionalExpression> queue = new Queue<ConditionalExpression>();
            queue.Enqueue(this);

            if (updates != null)
            {
                updates.ToList().ForEach(i => queue.Enqueue(i));
            }

            while (queue.Count > 0)
            {
                ConditionalExpression current = queue.Dequeue();
                if (current != null)
                {
                    children.Add(current);
                }

                if (current is LogicalOperation)
                {
                    queue.Enqueue((current as LogicalOperation).Left);
                    queue.Enqueue((current as LogicalOperation).Right);
                }
                else if (current is SelectMatchCriteria)
                {
                    queue.Enqueue((current as SelectMatchCriteria).Condition);
                }
                else if (current is InClause)
                {
                    queue.Enqueue((current as InClause).Condition1);
                    queue.Enqueue((current as InClause).Condition2);
                    queue.Enqueue((current as InClause).Child);
                }
                else if (current is FieldMatchCriteria && (current as FieldMatchCriteria).Value is SelectMatchCriteria)
                {
                    queue.Enqueue((current as FieldMatchCriteria).Value as SelectMatchCriteria);
                }
                else if (current is UnaryFunction)
                {
                    queue.Enqueue((current as UnaryFunction).Operand);
                }
                else if (current is BinaryFunction)
                {
                    queue.Enqueue((current as BinaryFunction).Operand1);

                    if ((current as BinaryFunction).Operand2 is ConditionalExpression)
                    {
                        queue.Enqueue((current as BinaryFunction).Operand2 as ConditionalExpression);
                    }
                }
                else if (current is NegationOperator)
                {
                    queue.Enqueue((current as NegationOperator).Operand);
                }
                else if (current is FieldMatchCriteria)
                {
                    queue.Enqueue((current as FieldMatchCriteria).Field);

                    if ((current as FieldMatchCriteria).Value is ConditionalExpression)
                    {
                        queue.Enqueue((current as FieldMatchCriteria).Value as ConditionalExpression);
                    }
                }
                else if (current is Update)
                {
                    queue.Enqueue((current as Update).ExpressionValue);
                }
                else if (current is HasClause)
                {
                    queue.Enqueue((current as HasClause).Condition);
                }
                else if (current is ContainsClause)
                {
                    queue.Enqueue((current as ContainsClause).Condition);
                }
            }

            return children;
        }



        public static ConditionalExpression operator &(ConditionalExpression operand1, ConditionalExpression operand2)
        {
            return new LogicalOperation() { Left = operand1, Right = operand2, Operator = LogicalOperator.And };
        }

        public static ConditionalExpression operator |(ConditionalExpression operand1, ConditionalExpression operand2)
        {
            return new LogicalOperation() { Left = operand1, Right = operand2, Operator = LogicalOperator.Or };
        }

        public static ConditionalExpression operator !(ConditionalExpression operand)
        {
            return new NegationOperator() { Operand = operand };
        }
    }
}
